#ifndef TOOLS_HPP
#define TOOLS_HPP

#include <iostream>	
#include <algorithm>
using namespace std;

// infos relatives à un appel de fonction
struct call_info
{
	unsigned int num_appel;
	string name;	
	int required;
	long int start;
	long int stop;
	long int inclusive_time;
	long int exclusive_time;
};

// infos relatives au comportement mémoire d'une fonction
struct static_info
{
	string name;	
	long int nb_loads;
	long int nb_stores;
};

// agrégation des données d'exécution pour une fonction
struct agreg_dyn
{
    string name;
    float ratio_exec;
    int nb_calls;    		// nombre d'appels
    long int sum_excl_time;    // somme des temps exclusifs
    float avg_excl_time;	// moyenne des temps exclusifs
};

// fusion des informations statiques et dynamiques pour une fonction
struct merged_info
{
    string name;
    float ratio_exec;
    int nb_calls;    
    long int sum_excl_time;
    float avg_excl_time;
		long int nb_loads;
    float ratio_loads;
		long int nb_stores;
    float ratio_stores;
};

ostream& operator << (ostream & out, const call_info item)
{
	out << item.num_appel <<"\t" << item.name << "\t\t"<< item.required << "\t"<< item.start << " " << item.stop << "  " << item.inclusive_time << "\t\t" << item.exclusive_time << endl;	
	return out;
}

ostream& operator << (ostream & out, const static_info item)
{
	out << item.name <<"\t" << item.nb_loads << "\t\t"<< item.nb_stores << endl;
	return out;
}

ostream& operator << (ostream & out, const agreg_dyn item)
{
	out << item.name <<"\t" << item.ratio_exec<< "\t"<< item.nb_calls << "\t"<< item.sum_excl_time <<"\t\t" << item.avg_excl_time <<endl;	
	return out;
}

ostream& operator << (ostream & out, const merged_info item)
{
	out << item.name <<"\t" << item.ratio_exec<< "\t"<< item.nb_calls << "\t"<< item.sum_excl_time <<"\t\t" << item.avg_excl_time <<"\t\t" << item.nb_loads <<"\t\t"<< item.nb_stores << "\t\t" << item.ratio_loads <<"\t\t"<< item.ratio_stores <<endl;	
	return out;
}

void display_vec(const vector<call_info> &v)
{
	if (!v.empty())
	{
		cout << "\nN°\tName\t\tReq\tEntry\t\t Exit \t\t   Incl_Time\tExcl_Time" << endl;		
		for_each(v.begin(), v.end(),
			[](call_info c)
			{
				cout << c;
			}
		);
	}
	else
		cout << "Vector is empty." << endl;
}

void display_vec(const vector<static_info> &v)
{
	if (!v.empty())
	{
		cout << "\nName\tNb_Loads\t\tNb_Stores\n" << endl;		
		for_each(v.begin(), v.end(),
			[](static_info c)
			{
				cout << c;
			}
		);
		cout << endl;
	}
	else
		cout << "Vector is empty." << endl;
}

void display_vec(const vector<agreg_dyn> &v)
{
	if (!v.empty())
	{
		cout << "\nname\tratio\t\tcalls\tSumExclT\tAvgExcl" << endl;		
		for_each(v.begin(), v.end(),
			[](agreg_dyn c)
			{
				cout << c;
			}
		);
		cout << endl;
	}
	else
		cout << "Vector is empty." << endl;
}

void display_vec(const vector<merged_info> &v)
{
	if (!v.empty())
	{
		cout << "\nname\tratio\t\tcalls\tSumExclT\tAvgExcl\t\tloads\t\tstores\t\tratio_loads\tratio_stores" << endl;		
		for_each(v.begin(), v.end(),
			[](merged_info c)
			{
				cout << c;
			}
		);
		cout << endl;
  }
	else
		cout << "Vector is empty." << endl;      
}


// Teste la fonction A a été appelée par la fonction B
int A_included_in_B(call_info A, call_info B)
{
	return ( (A.start >= B.start) && (A.stop <= B.stop) );
}

// calcul les temps exclusifs
void calc_exclusive_times(vector<call_info> &v)
{
	// commence par la fin
	v.back().exclusive_time = v.back().inclusive_time; 
	
	// remonte de l'avant dernier jusqu'au premier	
	for (int i = v.size()-2; i>=0; --i)					
	{
		v[i].exclusive_time = v[i].inclusive_time;
		int j=i+1;
		int fini = 0;
		while( !fini && j<v.size() )
		{
			if (A_included_in_B(v[j], v[i]))
			{
				v[i].exclusive_time -= v[j].exclusive_time;
			}
			else
			{
				fini = 1;
			}
			++j;
		}
	}
}

// calule la moyenne des temps exclusifs
void average_dynamic_infos(vector<call_info> &v, vector<agreg_dyn> &vagreg)
{
  //trier le vecteur par ordre alphabétique de fonction
	sort(v.begin(), v.end(), [](const call_info &c1, const call_info &c2){return c1.name < c2. name; });
    
  // récupération du temps total d'exécution
	std::vector<call_info>::iterator it = v.begin();
	while(it->name != "main")
	{
		it = ++it;
	}
    long int total_exec_time = it->inclusive_time;
    //cout << "inclusive time" << total_exec_time << endl;
    
    // variables auxiliaires
    agreg_dyn tmp;
    int cpt = 0;
    string last_fx=v.begin()->name;
    string current_fx="";
    long int sum_excl_time = 0;
    
    for (auto it=v.begin(); it<v.end(); ++it)
    {
        
        current_fx=it->name;        
        // si c'est la même fx
        if (current_fx == last_fx)
        {
            cpt++;
            sum_excl_time += it->exclusive_time;
        }
        else
        {           
            // écriture des résultats
            tmp.name = last_fx;
            tmp.nb_calls = cpt;
            tmp.ratio_exec = sum_excl_time*1.0/total_exec_time;
            tmp.sum_excl_time = sum_excl_time;
            tmp.avg_excl_time = sum_excl_time*1.0/cpt;
            vagreg.push_back(tmp);
                        
            // réinitialisation des compteurs
            cpt = 1;
            last_fx = current_fx;
            sum_excl_time = it->exclusive_time;            
        }
    }
    
	// ttmt de la dernière fonction
    tmp.name = last_fx;
    tmp.nb_calls = cpt;
    tmp.ratio_exec = sum_excl_time*1.0/total_exec_time;
    tmp.sum_excl_time = sum_excl_time;
    tmp.avg_excl_time = sum_excl_time*1.0/cpt;
    vagreg.push_back(tmp);
}

// fusionne les données statiques et le données dynamiques
void merge_static_dynamic_infos(vector<agreg_dyn> &vagreg, vector<static_info> &vstat, vector<merged_info> &recap)
{       
    // récupération des totaux statiques
    long int sum_loads = 0;
    long int sum_stores = 0;
    int cpt;
    
    for (auto itstat=vstat.begin(); itstat<vstat.end(); ++itstat)
    {
        // récupère le nb d'exécutions, dans le tableau dynamique
        bool found = false;
        for (auto itagreg=vagreg.begin(); itagreg<vagreg.end(); ++itagreg)
        {
            if (itagreg->name == itstat->name)
            {
                found = true;
                cpt = itagreg->nb_calls; 
                break;
            }
        }
        
        if (found)
        {                        
            sum_loads += itstat->nb_loads * cpt;
            sum_stores += itstat->nb_stores * cpt;
        }
    }
       
    merged_info tmp;    
    for (auto itstat=vstat.begin(); itstat<vstat.end(); ++itstat)
    {        
        // récupère  dans le tableau dynamique
        bool found = false;
        for (auto itagreg=vagreg.begin(); itagreg<vagreg.end(); ++itagreg)
        {
            if (itagreg-> name == itstat->name)
            {
                found = true;
                // récupération des résultats dynamiques                     
                tmp.ratio_exec = itagreg->ratio_exec; 
                tmp.sum_excl_time = itagreg->sum_excl_time;
                tmp.avg_excl_time = itagreg->avg_excl_time;                
                tmp.nb_calls = itagreg->nb_calls;
                break;
            }
        }
        
        if (found)
        {            
            // récupération des résultats statiques
            tmp.name = itstat->name;
            tmp.nb_loads = itstat->nb_loads*tmp.nb_calls;
            tmp.nb_stores = itstat->nb_stores*tmp.nb_calls;  
            tmp.ratio_loads = tmp.nb_loads*1.0/sum_loads;
            tmp.ratio_stores = tmp.nb_stores*1.0/sum_stores;
        }
        else
        {
            tmp.name = itstat->name;
            tmp.ratio_exec = 0;
            tmp.nb_calls = 0;    
            tmp.sum_excl_time = 0;
            tmp.avg_excl_time = 0;
            tmp.nb_loads = 0;
            tmp.ratio_loads = 0;
            tmp.nb_stores = 0;
            tmp.ratio_stores = 0;
        }     
        recap.push_back(tmp);
    }
}

// vérification des temps exclusifs
// somme des temps exclusifs de tous les appels de fonction = temps inclusif de la fonction main
void verif_times(vector<call_info> &v)
{
	int sum_excl = 0;
	for_each(v.begin(), v.end(),
		[&sum_excl](call_info c)
		{
			sum_excl+=c.exclusive_time;
		}
	);
	cout <<"Somme des Temps Exclusifs\t" << sum_excl << endl;
	
	std::vector<call_info>::iterator it = v.begin();
	while(it->name != "main")
	{
		it = ++it;
	}
	cout <<"Temps Inclusif du Main\t\t" << it->inclusive_time << endl;
	cout << endl;
}

// écrit les données de chaque fonction à plotter
void write_data(vector<call_info> &v)
{
	//trier le vecteur par ordre alphabétique de fonction
	sort(v.begin(), v.end(), [](const call_info &c1, const call_info &c2){return c1.name < c2. name; });
		
	//ecrire les fichiers data de chaque fonction
	fstream out;
	out.exceptions(ifstream::failbit|ifstream::badbit);
	
	string last_fx="";
	int cpt;
	for(auto it = v.begin(); it<v.end(); ++it)
	{
		int required = it->required;
		string current_fx = it->name;
		
		if ((current_fx != last_fx) && required)	// nouvelle fonction ET required
		{
			try
			{
				// si ce n'est pas la 1ère fonction, fermeture du fichier précédent
				if (last_fx != "")
				{
					out.close();
				}
				
				// ouverture du fichier pour la fx courante
				out.open("data/"+current_fx, ifstream::out);
				cpt = 1;
				out << cpt << " " << it->inclusive_time << " " << it-> exclusive_time << endl;
				last_fx = current_fx;
				
			}
			catch(ifstream::failure e)
			{
				cerr << "[Error caught] " << e.what() << endl;
				exit(1);
			}
		}
		else if (current_fx == last_fx) // fx inchangée, nouvelle ligne
		{
			++cpt;
			out << cpt << " " << it->inclusive_time << " " << it-> exclusive_time << endl;
		}
	}	
	//fermeture du dernier fichier
	out.close();	
}

// écrit les données de l'histogramme final
void write_comp_data(const vector<merged_info> &v)
{
		fstream out;
		out.exceptions(ifstream::failbit|ifstream::badbit);
	
    try
    {
        out.open("data/comp", ifstream::out);
        for (auto it=v.begin(); it<v.end(); ++it)
        {
            out << it->name << " " << it->ratio_exec << " " << it->ratio_loads << " " << it->ratio_stores << endl;
        }
        out.close();            
    }
    catch(ifstream::failure e)
    {
        cerr << "[Error caught] " << e.what() << endl;
        exit(1);
    }
}

void write_averages(const vector<merged_info> &v)
{
    // ecrire les fichiers data_avg de chaque fonction
    fstream out;
	out.exceptions(ifstream::failbit|ifstream::badbit);

    for (auto it= v.begin(); it<v.end(); ++it)
    {
        try
        {
            out.open("data/"+it->name+"_avg", ifstream::out);
            out << it->avg_excl_time;
            out.close();
        }
        catch(ifstream::failure e)
        {
            cerr << "[Error caught] " << e.what() << endl;
            exit(1);
        }
    }
}

// écrit le fichier .gp contenant les commandes à passer à gnuplot
void write_gp(const vector<call_info> &v)
{
			
	//ecrire les fichiers data de chaque fonction
	fstream out;
	out.exceptions(ifstream::failbit|ifstream::badbit);
		
	//ouverture du fichier
	try
	{
		out.open("plotter/plotter.gp", ifstream::out);
		out << "set terminal pdf mono size 15cm, 8cm" << endl;
		out << "set output \"profiler.pdf\"" << endl;
		out << "set boxwidth 0.2" << endl;
		out << "set style fill solid" << endl;
		out << "set xlabel \"Executions\"" << endl;
		out << "set ylabel \"Time in Microseconds\"" << endl;
		out << "set yrange[0:*]" << endl;
	}
	catch(ifstream::failure e)
	{
		cerr << "[Error caught] " << e.what() << endl;
		exit(1);
	}
	
	string last_fx="";
	for(auto it = v.begin(); it<v.end(); ++it)
	{
		int required = it->required;
		string current_fx = it->name;
		
		if ((current_fx != last_fx) && required)	// nouvelle fonction ET required
		{
        out <<endl<< "set title \"" << it->name << "\"" << endl;
        out << "plot \t\"../data/" << it->name << "\" using 3:xtic(1) title \"Exclusive Time\" with boxes linecolor rgb \"green\",\\" << endl;
        out << "\t\t\"../data/" <<it->name << "\" using 2:xtic(1) title \"Inclusive Time\" with lines linecolor rgb \"blue\" " << endl; 
        last_fx = current_fx;
		}
	}
	
	//fermeture du dernier fichier
	out.close();	
}

#endif
